package com.hose.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.microsoft.playwright.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

import static cn.hutool.core.date.DatePattern.PURE_DATETIME_MS_PATTERN;

public class HoseUtils {
    private static final Logger logger = LoggerFactory.getLogger(HoseUtils.class);
    private static final ThreadLocal<Map<String, Object>> threadLocal = new InheritableThreadLocal<>();

    /**
     * 创建Context
     *
     * @param browser
     * @param sessionFile
     * @return
     */
    public static BrowserContext createContext(Browser browser, String sessionFile, String userAgent) {
        userAgent = Objects.isNull(userAgent) ? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" : userAgent;
        Browser.NewContextOptions newContextOptions = new Browser.NewContextOptions();
        String sessionContext = Objects.isNull(sessionFile) ? null : FileUtil.readString(FileUtil.file(sessionFile), Charset.forName("UTF-8"));
        newContextOptions
                .setAcceptDownloads(true)
                .setLocale("zh-CN")
                .setStorageState(sessionContext)
                .setUserAgent(userAgent);
        return browser.newContext(newContextOptions);
    }

    /***
     * 智能等待，等待当前操作，所触发的所有网络请求完成后，再进行下一步操作。
     * @param page
     */
    public static void wait(Page page) {
        logger.info("智能等待..star.... ");
        HoseUtils.wait(page, () -> {});
        logger.info("智能等待..end.... ");
    }

    /***
     * 智能等待，等待当前操作，所触发的所有网络请求完成后，再进行下一步操作。
     * @param page
     * @param runnable 执行动作
     */
    public static void wait(Page page, Runnable runnable) {
        HoseUtils.wait(page, 500d, 500d, 0, runnable);
    }

    /***
     * 智能等待，等待当前操作，所触发的所有网络请求完成后，再进行下一步操作。
     * @param page
     * @param initWaitTime 前置等待时间
     * @param circleWaitTime 500ms没有请求，结束
     * @param afterTimeout 没有操作后等待
     * @param runnable 执行动作
     */
    public static void wait(Page page, double initWaitTime, double circleWaitTime, double afterTimeout, Runnable runnable) {
        Map<String, Object> urlDict = threadLocal.get();
        if (Objects.isNull(urlDict)) {
            urlDict = new HashMap<>();
            threadLocal.set(new HashMap<>());
        }
        // 清空，防止上次等待穿透至本次
        urlDict.clear();
        //前置：添加监听Request请求，并放至当前Map中
        page.onRequest(beforeProcess());
        page.waitForTimeout(initWaitTime);
        // 执行：页面操作
        runnable.run();
        // 后续：操作完成后，等待所有网络请求完成
        afterProcess(page, circleWaitTime, afterTimeout);
    }


    /**
     * 前置：添加监听Request请求，并放至当前Map中     *
     */
    private static Consumer<Request> beforeProcess() {
        return request -> {
            Map<String, Object> finalUrlDict = threadLocal.get();
            String url = request.url();
            if (StrUtil.contains(url, "/api/") && !StrUtil.equals("text/event-stream", request.headers().get("accept"))) {
                long requestStartTime = DateUtil.current();
                String urlStartKey = StrUtil.format("{}-{}", IdUtil.simpleUUID(), requestStartTime);
                //logger.info("========URL添加 - 开始: {}========", urlStartKey);
                // 添加请求URL
                finalUrlDict.put(urlStartKey, "S-"+url);
                // 异步分叉
                Response response = request.response();
                String urlEndKey = StrUtil.format("{}-E-{}", requestStartTime, url);
                //# 过滤掉返回错误的请求 //# 当请求返回非 200 时，等待请求 finished
                if (Objects.isNull(response)) {
                    finalUrlDict.put(urlStartKey, "E-"+url);
                    return;
                }
                int status = response.status();
                if (status == 200) {
                    response.finished();
                }
                //logger.info("========URL添加 - 删除: {}========", urlEndKey);
                finalUrlDict.put(urlStartKey, "E-"+url);
            }
        };
    }

    /***
     * 操作完成后，等待所有网络请求完成
     * @param page
     * @param circleWaitTime
     * @param afterTimeout     *
     */
    private static void afterProcess(Page page, double circleWaitTime, double afterTimeout) {
        int initSize = threadLocal.get().size();
        int count = 0;
        while (true) {
            count = count + 1;
            // logger.info("======== URL等待，第 {} 次, urlCount:{}========", count, threadLocal.get().size());
            if (count > 30) {
                // logger.info("========URL等待，超出30秒，全部清空 ========");
                threadLocal.get().clear();
                break;
            }
            if (threadLocal.get().size() == initSize) {
                page.waitForTimeout(circleWaitTime);
                if (threadLocal.get().size() == initSize) {
                    threadLocal.get().clear();
                    break;
                }
            } else {
                initSize = threadLocal.get().size();
                page.waitForTimeout(circleWaitTime);
            }
        }
        page.offRequest(beforeProcess());
        if (afterTimeout > 0d) {
            page.waitForTimeout(afterTimeout);
        }
        // 再清空一次，防止对象穿透至下一次等待，
        // 经测试，此处依然解决不了穿透问题，因 page.offRequest(beforeProcess()); 是异步。
        // 终极方案：在入口处 进行清空，来防止 上次引起污染
        threadLocal.get().clear();
        // logger.info("======== URL等待，全部结束, urlCount:{}========", count, threadLocal.get().size());
    }


}
